/**
 * 在构造函数或同步方法执行前执行一个函数fn
 * 如果设置stop为true那么fn可以返回一个true来中止原函数的执行，中止执行调用方将得到一个返回值null
 * 
 * @export
 * @param {(self: any, target: Function, ...args: any[]) => void} fn
 * @param {boolean} [stop=false]
 * @returns
 */
export function Before(fn: (self: any, target: Function, ...args: any[]) => void, stop: boolean = false) {
    return function (target: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor) {
        return aopGenerator(target, descriptor, function (targetFn, self, args) {
            let breaked = null, result = null;
            breaked = fn.apply(self, [self, targetFn].concat(args));
            if (!(stop && breaked)) {
                result = targetFn.apply(self, args);
            }
            return result;
        }) as any;
    };
}


/**
 * 在构造函数或方法执行后执行一个函数
 * 
 * @export
 * @param {(self: any, target: Function, ...args: any[]) => void} fn
 * @returns
 */
export function After(fn: (self: any, target: Function, result: any, ...args: any[]) => void, warn = true) {
    return function (target: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor) {
        return aopGenerator(target, descriptor, function (targetFn, self, args) {
            let result = targetFn.apply(self, args);
            if (warn && isPromise(result)) {
                let {isClass} = isDecorateClass(target, descriptor),
                    className = isClass ? targetFn.name : (target.constructor as Function).name;
                console.warn(`%cWARING:
                    After装饰器适合用来装饰同步方法
                    但%c${className}->${propertyKey}%c看起来像是一个异步方法
                    异步方法应该使用AfterAsync装饰
                    如果你确信你的操作无误，你可以将After装饰器的第二个参数warn设置为false关闭此检查。`, 'color:black', 'color: red;padding:0 20px;font-size:1.2em;', 'color:black');
            }
            fn.apply(self, [self, targetFn, result].concat(args));
            return result;
        });
    } as any;
}

/**
 * 在异步方法执行后执行一个函数
 * 
 * @export
 * @param {(self: any, target: Function, ...args: any[]) => void} fn
 * @param {boolean} [stop=false]
 * @returns
 */
export function AfterAsync(fn: (self: any, target: Function, ...args: any[]) => void, stop: boolean = false) {
    return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
        return aopGenerator(target, descriptor, function (targetFn, self, args) {
            let promise = <Promise<any>>targetFn.apply(self, args),
                {isClass} = isDecorateClass(target, descriptor),
                className = isClass ? targetFn.name : (target.constructor as Function).name;
            if (isPromise(promise)) {
                promise.then((result) => {
                    fn.apply(self, [self, targetFn, result].concat(args));
                });
            } else {
                throw new TypeError(`AfterAsync装饰器必须装饰一个异步方法,${className}->${propertyKey || ''}不是一个异步方法。`);
            }
            return promise;
        });
    };
}


/**
 * 当构造函数或方法执行错误时试行一个函数
 * 
 * @export
 * @param {(self: any, target: Function, ...args: any[]) => void} fn
 * @returns
 */
export function WhenError(fn: (self: any, target: Function, err: Error, ...args: any[]) => void) {
    return function (target: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor) {
        return aopGenerator(target, descriptor, function (targetFn, self, args) {
            try {
                return targetFn.apply(self, args);
            } catch (e) {
                return fn.apply(null, [self, targetFn, e].concat(args));
            }
        });
    } as any;
}


/**
 * AOP装饰器生成器
 * 
 * @param {Function} target
 * @param {PropertyDescriptor} descriptor
 * @param {(target: Function, self: any, args: any[]) => void} aop
 * @returns
 */
export function aopGenerator(target: Function, descriptor: PropertyDescriptor, aop: (targetFn: Function, self: any, args: any[]) => void): Function | PropertyDescriptor {
    // 判断是否装饰的是类构造函数
    let {isClass, noBoth} = isDecorateClass(target, descriptor);
    if (noBoth) {
        throw new TypeError(`aopGenerator 仅支持类构造函数或类方法`);
    }
    let targetFn: Function = isClass ? target : descriptor.value,
        wapper: Function = null;
    if (!isConstructor(targetFn)) {
        throw new Error(`仅允许装饰一个方法或一个类`);
    }

    // 返回一个同名函数，如果装饰的是一个组件类的话，React调试工具看到还是和原来一样的组件名
    wapper = copyFunctionProp(createFunctionWithName(targetFn.name, function (self, args: any[]) {
        return aop(targetFn, self, args);
    }), targetFn);

    // 如果装饰的是一个类则需要处理继承关系&拷贝所有静态属性
    if (isClass) {
        return wapper;
    }
    descriptor.value = wapper;
    return descriptor;
}

/**
 * 创建一个指定名称的函数
 * 
 * @param {string} name
 * @param {Function} fn
 * @returns {Function}
 */
export function createFunctionWithName(name: string, fn: Function): Function {
    /**
     * 创建的函数需要指定严格模式，否则当this为null时默认指向window对象
     * eg:
     *  function xx(){ console.log(this) }
     * 严格模式下： xx.call(null) // 输出: null
     * 非严格模式下: xx.call(null) // 输出: window
     */
    return Function('fn', `return function ${name}(){ 'use strict'; return fn.call(this, this, [].slice.call(arguments)); }`)(fn);
}


/**
 * 判断一个值是否可以作为一个构造函数
 * 
 * @param {*} fn
 * @returns
 */
export function isConstructor(fn: any) {
    return typeof fn === 'function';
}
/**
 * 判断一个值是否是一个函数
 */
export const isFunction = isConstructor;
/**
 * 复制函数属性
 * 
 * @export
 * @param {Function} target
 * @param {Function} source
 * @returns
 */
export function copyFunctionProp(target: Function, source: Function) {
    if (!(isConstructor(target) && isConstructor(source))) {
        throw new TypeError('必须都都是Function')
    }
    Object.getOwnPropertyNames(source).forEach(name => {
        try {
            target[name] = source[name];
        } catch (e) { }
    });
    Object.setPrototypeOf(target, Object.getPrototypeOf(source));
    return target;
}


/**
 * 判断是否是装饰一个类或方法或者二者都不是
 * 
 * @export
 * @param {*} target
 * @param {PropertyDescriptor} descriptor
 * @returns
 */
export function isDecorateClass(target: any, descriptor: PropertyDescriptor) {
    let isClass = true, noBoth = false;
    // 如果是属性的话现价段没有descriptor，为了健壮性依然判断descriptor.value的属性
    if (descriptor) {
        if (isConstructor(descriptor.value)) {
            isClass = false;
        } else {
            noBoth = true;
        }
    } else if (!isConstructor(target)) {
        noBoth = true;
    }
    return {
        isClass,
        noBoth
    };
}
/**
 * 判断一个对象是否是Promise对象
 * 
 * @export
 * @param {Promise<any>} obj
 * @returns
 */
export function isPromise(obj: Promise<any>) {
    return obj && obj.then && typeof obj.then === 'function' && obj.catch && typeof obj.catch === 'function';
}